{*******************************************************************************
* The contents of this file are used with permission, subject to the Mozilla   *
* Public License Version 1.1 (the "License"); you may not use this file except *
* in compliance with the License. You may obtain a copy of the License at      *
* http://www.mozilla.org/MPL/                                                  *
*                                                                              *
* Software distributed under the License is distributed on an "AS IS" basis,   *
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for *
* the specific language governing rights and limitations under the License.    *
*                                                                              *
* The Original Code is distributed as part of the "mediate" product and is     *
* Copyright (C) @home media limited. All Rights Reserved.                      *
*                                                                              *
* Email: support@athomemedia.co.uk                                             *
* Web:   http://www.athomemedia.co.uk                                          *
*******************************************************************************}

unit Functions;

interface

uses Classes, Types, AHMTypes, UIObjects, MediaManager, XMLHelper;

const
  // Function names
  FN_NONE      = 'none';
  FN_QUIT      = 'quit';
  FN_RESTART   = 'restart';
  FN_SLEEP     = 'sleep';
  FN_POWEROFF  = 'poweroff';
  FN_REBOOT    = 'reboot';
  FN_HIBERNATE = 'hibernate';
  FN_WAKESCRN  = 'wakescreen';
  FN_SLEEPSCRN = 'sleepscreen';
  FN_ALTTAB    = 'alttab';
  FN_ACTION    = 'action';
  FN_PAGEUP    = 'pageup';
  FN_PAGEDN    = 'pagedn';
  FN_PAGELF    = 'pagelf';
  FN_PAGERT    = 'pagert';
  FN_FOCUSUP   = 'focusup';
  FN_FOCUSDN   = 'focusdn';
  FN_FOCUSLF   = 'focuslf';
  FN_FOCUSRT   = 'focusrt';
  FN_SANE      = 'sane';
  FN_FOCUS     = 'focus';
  FN_PRESS     = 'press';
  FN_CANCEL    = 'cancel';
  FN_PARENT    = 'parent';
  FN_ROOT      = 'root';
  FN_CHECK     = 'check';
  FN_UNCHECK   = 'uncheck';
  FN_CHECKTGL  = 'checktoggle';
  FN_ENABLE    = 'enable';
  FN_DISABLE   = 'disable';
  FN_MSENABLE  = 'enablemouse';
  FN_MSDISABLE = 'disablemouse';
  FN_SHOW      = 'show';
  FN_HIDE      = 'hide';
  FN_SHOWCTRL  = 'showcontrol';
  FN_HIDECTRL  = 'hidecontrol';
  FN_SHOWCNTNR = 'showcontainer';
  FN_HIDECNTNR = 'hidecontainer';
  FN_REFRESH   = 'refresh';
  FN_SETSTYLE  = 'setstyle';
  FN_ANIMCTRL  = 'animatecontrol';
  FN_ANIMCNTR  = 'animatecontainer';
  FN_SENDKEY   = 'sendkey';
  FN_JUMP      = 'jump';
  FN_IFJUMP    = 'ifjump';
  FN_LINK      = 'link';
  FN_OVERLAY   = 'overlay';
  FN_CLOSE     = 'close';
  FN_STARTTMR  = 'starttimer';
  FN_RESETTMR  = 'resettimer';
  FN_STOPTMR   = 'stoptimer';
  FN_SOUND     = 'sound';
  FN_DELAY     = 'delay';
  FN_DIAG      = 'diag';
  FN_PLAY      = 'play';
  FN_PAUSE     = 'pause';
  FN_PLAYPAUSE = 'playpause';
  FN_FFWD      = 'ffwd';
  FN_RWND      = 'rwnd';
  FN_FSKIP     = 'fskip';
  FN_BSKIP     = 'bskip';
  FN_STOP      = 'stop';
  FN_SETMETA   = 'setmeta';
  FN_CLEARMETA = 'clearmeta';
  FN_NEXTMETA  = 'nextmeta';
  FN_PREVMETA  = 'prevmeta';
  FN_INCMETA   = 'incmeta';
  FN_DECMETA   = 'decmeta';
  FN_LOADMETA  = 'loadmeta';
  FN_SAVEMETA  = 'savemeta';
  FN_MUTE      = 'mute';
  FN_INCVOL    = 'incvol';
  FN_DECVOL    = 'decvol';
  FN_MACRO     = 'macro';
  FN_IFMACRO   = 'ifmacro';
  FN_CLEARPL   = 'clearpl';
  FN_ADDTOPL   = 'addtopl';
  FN_APPENDPL  = 'appendpl';
  FN_INSERTPL  = 'insertpl';
  FN_REPLACEPL = 'replacepl';
  FN_RESTARTPL = 'restartpl';
  FN_LOADPLUG  = 'loadplugin';
  FN_UNLDPLUG  = 'unloadplugin';
  FN_STARTPLUG = 'startplugin';
  FN_STOPPLUG  = 'stopplugin';
  FN_UPDTPLUG  = 'updateplugin';
  FN_SHOWPLUG  = 'showplugin';
  FN_HIDEPLUG  = 'hideplugin';
  FN_INTERNET  = 'browseurl';
  FN_CONFIGURE = 'configure';
  FN_ADDPRESET = 'addradiopreset';
  FN_DELPRESET = 'deleteradiopreset';

type
  // Function target types
  TAHMTargetType = (ttNone, ttScreen, ttDescriptor, ttText, ttControl, ttSound,
                    ttValue, ttMeta, ttMacro, ttPlaylist, ttPlugin, ttAction,
                    ttTimer, ttContainer);

  TAHMFunctions = class
  private
    FDelayTimestamp: TAHMTimestamp;
    FDelayRunning: Boolean;
    FApplicationIndex: Integer;
  public
    procedure Quit;
    procedure Restart;
    procedure Sleep;
    procedure PowerOff;
    procedure Reboot;
    procedure Hibernate;
    procedure WakeScreen;
    procedure SleepScreen;
    procedure SwitchApplication;
    procedure CallAction(ActionName: String);
    procedure PlayMedia(PlaylistName: String);
    procedure PauseMedia(PlaylistName: String);
    procedure PlayPauseMedia(PlaylistName: String);
    procedure FForwardMedia(PlaylistName: String);
    procedure RewindMedia(PlaylistName: String);
    procedure ForwardSkipMedia(PlaylistName: String);
    procedure BackwardSkipMedia(PlaylistName: String);
    procedure StopMedia(PlaylistName: String);
    procedure MuteMedia;
    procedure IncreaseVolume;
    procedure DecreaseVolume;
    procedure ClearPlaylist(PlaylistName: String);
    procedure AddToPlaylist(PlaylistName: String);
    procedure AppendPlaylist(PlaylistName: String);
    procedure InsertPlaylist(PlaylistName: String);
    procedure ReplacePlaylist(PlaylistName: String);
    procedure RestartPlaylist(PlaylistName: String);
    procedure ShowDiagnostics;
    procedure ShowConfiguration;
    procedure ResetScreenPosition;
    procedure MoveFocus(Direction: String);
    function ScrollPage(Direction: String; ControlName: String): TAHMFunctionResult;
    procedure SetContainerVisible(ContainerName: String; Visible: Boolean);
    procedure SetControlVisible(ControlName: String; Visible: Boolean);
    procedure SetControlEnabled(ControlName: String; Enabled: Boolean);
    procedure SetControlMouseEnabled(ControlName: String; Enabled: Boolean);
    procedure SetControlChecked(ControlName: String; Checked: TAHMCheckState);
    procedure Focus(ControlName: String);
    procedure Press(ControlName: String);
    procedure Check(ControlName: String);
    procedure UnCheck(ControlName: String);
    procedure ToggleCheck(ControlName: String);
    procedure Cancel(ControlName: String);
    procedure Parent(ControlName: String);
    procedure Root(ControlName: String);
    procedure Enable(ControlName: String);
    procedure Disable(ControlName: String);
    procedure MouseEnable(ControlName: String);
    procedure MouseDisable(ControlName: String);
    procedure Show(ControlName: String);
    procedure Hide(ControlName: String);
    procedure ShowContainer(ContainerName: String);
    procedure HideContainer(ContainerName: String);
    procedure Refresh(ControlName: String);
    procedure SetStyle(ControlName: String; StyleName: String);
    procedure AnimateControl(ControlName: String; AnimatorName: String);
    procedure AnimateContainer(ContainerName: String; AnimatorName: String);
    procedure SendKey(ControlName: String; KeyName: String);
    procedure Link(DescriptorName: String);
    procedure Jump(ScreenName: String);
    procedure ConditionalJump(ScreenName: String; Expression: String);
    procedure Overlay(ScreenName: String);
    procedure Close(ScreenName: String);
    procedure Sound(SoundName: String);
    function Delay(Millisecs: String): TAHMFunctionResult;
    procedure SetMeta(MetaItem: String; Value: String);
    procedure ClearMeta(MetaItem: String);
    procedure NextMeta(MetaItem: String; MetaGroup: String);
    procedure PrevMeta(MetaItem: String; MetaGroup: String);
    procedure IncMeta(MetaItem: String; MetaGroup: String);
    procedure DecMeta(MetaItem: String; MetaGroup: String);
    procedure LoadMeta(MetaGroup: String);
    procedure SaveMeta(MetaGroup: String);
    procedure CallMacro(MacroName: String);
    procedure ConditionalMacro(MacroName: String; Expression: String);
    function LoadPlugin(PluginName: String): TAHMFunctionResult;
    function UnloadPlugin(PluginName: String): TAHMFunctionResult;
    function StartPlugin(PluginName: String): TAHMFunctionResult;
    function StopPlugin(PluginName: String): TAHMFunctionResult;
    function UpdatePlugin(PluginName: String): TAHMFunctionResult;
    function ShowPlugin(PluginName, ControlName: String): TAHMFunctionResult;
    function HidePlugin(PluginName, ControlName: String): TAHMFunctionResult;
    procedure BrowseInternet(URL: String);
    procedure StartTimer(TimerName: String);
    procedure ResetTimer(TimerName: String);
    procedure StopTimer(TimerName: String);
    procedure AddRadioPreset;
    procedure DeleteRadioPreset;
  end;

  TAHMFunction = class(TAHMUIObject)
  private
    FTargetType: TAHMTargetType;
  protected
    function TargetType2Str(Value: TAHMTargetType): String;
    function Str2TargetType(Value: String): TAHMTargetType;
  public
    constructor Create(const Name: String); override;
    function RenderPreview: Boolean; override;
    function Execute: TAHMFunctionResult; overload;
    function Execute(TargetName: String; Parameter: String): TAHMFunctionResult; overload;
    procedure Assign(Source: TPersistent); override;
    procedure SaveAsXML(Lines: TStrings); override;
    procedure ParseFromXML(XML: TAHMXMLHelper); override;
  published
    property TargetType: TAHMTargetType read FTargetType write FTargetType;
  end;

  TAHMFunctionList = class(TAHMUIObjectList)
  private
  protected
  public
    constructor Create; override;
    function AddFunction(const InheritFrom, NewName: String): TAHMFunction;
    function GetFunction(const Name: String): TAHMFunction;
  end;

var
  FunctionsInstance: TAHMFunctions;

implementation

uses SysUtils, Forms, TagNames, XMLTags, ScreenForm, ApplicationManager, Screens,
  BaseControls, UIControls, Containers, Sounds, Screen, DirectXHelper, MetaData,
  MetaConsts, Logging, Timers, Timing, Macros, Animators, Utilities;


constructor TAHMFunctionList.Create;
begin
  inherited Create;

  XMLTagName := TAG_FUNCTIONS;
  ChildClass := TAHMFunction;
  FSpaceElements := False;
end;

function TAHMFunctionList.AddFunction(const InheritFrom, NewName: String): TAHMFunction;
begin
  Result := TAHMFunction(Inherit(InheritFrom, NewName));
end;

function TAHMFunctionList.GetFunction(const Name: String): TAHMFunction;
begin
  Result := TAHMFunction(GetObject(Name));
end;


//----------------------------------------------------------------------------//
constructor TAHMFunction.Create(const Name: String);
begin
  inherited Create(Name);

  XMLTagName := TAG_FUNCTION;
  ObjectType := otFunctions;
  ImageIndex := Ord(otFunctions);
end;

function TAHMFunction.TargetType2Str(Value: TAHMTargetType): String;
begin
  if Value = ttScreen then Result := ATTR_VAL_SCREEN
  else if Value = ttContainer then Result := ATTR_VAL_CONTAINER
  else if Value = ttDescriptor then Result := ATTR_VAL_DESCRIPTOR
  else if Value = ttText then Result := ATTR_VAL_TEXT
  else if Value = ttControl then Result := ATTR_VAL_CONTROL
  else if Value = ttSound then Result := ATTR_VAL_SOUND
  else if Value = ttValue then Result := ATTR_VAL_VALUE
  else if Value = ttMeta then Result := ATTR_VAL_META
  else if Value = ttMacro then Result := ATTR_VAL_MACRO
  else if Value = ttPlaylist then Result := ATTR_VAL_PLAYLIST
  else if Value = ttPlugin then Result := ATTR_VAL_PLUGIN
  else if Value = ttAction then Result := ATTR_VAL_ACTION
  else if Value = ttTimer then Result := ATTR_VAL_TIMER
  else Result := '';
end;

function TAHMFunction.Str2TargetType(Value: String): TAHMTargetType;
begin
  if Value = ATTR_VAL_SCREEN then Result := ttScreen
  else if Value = ATTR_VAL_CONTAINER then Result := ttContainer
  else if Value = ATTR_VAL_DESCRIPTOR then Result := ttDescriptor
  else if Value = ATTR_VAL_TEXT then Result := ttText
  else if Value = ATTR_VAL_CONTROL then Result := ttControl
  else if Value = ATTR_VAL_SOUND then Result := ttSound
  else if Value = ATTR_VAL_VALUE then Result := ttValue
  else if Value = ATTR_VAL_META then Result := ttMeta
  else if Value = ATTR_VAL_MACRO then Result := ttMacro
  else if Value = ATTR_VAL_PLAYLIST then Result := ttPlaylist
  else if Value = ATTR_VAL_PLUGIN then Result := ttPlugin
  else if Value = ATTR_VAL_ACTION then Result := ttAction
  else if Value = ATTR_VAL_TIMER then Result := ttTimer
  else Result := ttNone;
end;

function TAHMFunction.RenderPreview: Boolean;
begin
{$IFDEF DESIGNER}
  with ScreenInstance.Viewport do
  begin
    Canvas.DesignerBackground(ViewportRect);
    Canvas.TextRect(ViewportRect, nil, 'Function Properties' + sLineBreak +
                    sLineBreak + 'Target Type: ' + TargetType2Str(FTargetType) +
                    sLineBreak + 'Description: ' + Description);
  end;
{$ENDIF}
  Result := True;
end;

function TAHMFunction.Execute: TAHMFunctionResult;
begin
  // Set default result - function executed
  Result := frSuccess;

  if FTargetType = ttNone then
  begin
    // Perform selected function
    if Name = FN_NONE then Exit
    else if Name = FN_QUIT then FunctionsInstance.Quit
    else if Name = FN_RESTART then FunctionsInstance.Restart
    else if Name = FN_SLEEP then FunctionsInstance.Sleep
    else if Name = FN_POWEROFF then FunctionsInstance.PowerOff
    else if Name = FN_REBOOT then FunctionsInstance.Reboot
    else if Name = FN_HIBERNATE then FunctionsInstance.Hibernate
    else if Name = FN_WAKESCRN then FunctionsInstance.WakeScreen
    else if Name = FN_SLEEPSCRN then FunctionsInstance.SleepScreen
    else if Name = FN_ALTTAB then FunctionsInstance.SwitchApplication
    else if Name = FN_INCVOL then FunctionsInstance.IncreaseVolume
    else if Name = FN_DECVOL then FunctionsInstance.DecreaseVolume
    else if Name = FN_MUTE then FunctionsInstance.MuteMedia
    else if Name = FN_DIAG then FunctionsInstance.ShowDiagnostics
    else if Name = FN_CONFIGURE then FunctionsInstance.ShowConfiguration
    else if Name = FN_SANE then FunctionsInstance.ResetScreenPosition
    else if Name = FN_FOCUSUP then FunctionsInstance.MoveFocus(Name)
    else if Name = FN_FOCUSDN then FunctionsInstance.MoveFocus(Name)
    else if Name = FN_FOCUSLF then FunctionsInstance.MoveFocus(Name)
    else if Name = FN_FOCUSRT then FunctionsInstance.MoveFocus(Name)
    else if Name = FN_ADDPRESET then FunctionsInstance.AddRadioPreset
    else if Name = FN_DELPRESET then FunctionsInstance.DeleteRadioPreset
    else
      raise EFunctionException.Create('Performing unsupported function: ' + Name + ' (' + Description + ')');
  end
  else
    raise EFunctionException.Create('Target name expected for function ' + Name);
end;

function TAHMFunction.Execute(TargetName: String; Parameter: String): TAHMFunctionResult;
var
  MTargetName, MParameter: String;
begin
  // Set default result - function executed
  Result := frSuccess;

  if (FTargetType <> ttNone) then
    if (TargetName <> '') then
    begin
      // Perform macro substitution on function target name & parameter
      MTargetName := ApplicationManagerInstance.MetaDataManager.ProcessMarkup(TargetName);
      MParameter := ApplicationManagerInstance.MetaDataManager.ProcessMarkup(Parameter);

      // Do stuff with target
      if FTargetType = ttControl then
      begin
        if Name = FN_PRESS then FunctionsInstance.Press(MTargetName)
        else if Name = FN_FOCUS then FunctionsInstance.Focus(MTargetName)
        else if Name = FN_CANCEL then FunctionsInstance.Cancel(MTargetName)
        else if Name = FN_PARENT then FunctionsInstance.Parent(MTargetName)
        else if Name = FN_ROOT then FunctionsInstance.Root(MTargetName)
        else if Name = FN_CHECK then FunctionsInstance.Check(MTargetName)
        else if Name = FN_UNCHECK then FunctionsInstance.UnCheck(MTargetName)
        else if Name = FN_CHECKTGL then FunctionsInstance.ToggleCheck(MTargetName)
        else if Name = FN_ENABLE then FunctionsInstance.Enable(MTargetName)
        else if Name = FN_DISABLE then FunctionsInstance.Disable(MTargetName)
        else if Name = FN_MSENABLE then FunctionsInstance.MouseEnable(MTargetName)
        else if Name = FN_MSDISABLE then FunctionsInstance.MouseDisable(MTargetName)
        else if Name = FN_SHOW then FunctionsInstance.Show(MTargetName) // deprecated
        else if Name = FN_HIDE then FunctionsInstance.Hide(MTargetName) // deprecated
        else if Name = FN_SHOWCTRL then FunctionsInstance.Show(MTargetName)
        else if Name = FN_HIDECTRL then FunctionsInstance.Hide(MTargetName)
        else if Name = FN_SETSTYLE then FunctionsInstance.SetStyle(MTargetName, MParameter)
        else if Name = FN_REFRESH then FunctionsInstance.Refresh(MTargetName)
        else if Name = FN_ANIMCTRL then FunctionsInstance.AnimateControl(MTargetName, MParameter)
        else if Name = FN_ANIMCNTR then FunctionsInstance.AnimateContainer(MTargetName, MParameter)
        else if Name = FN_SENDKEY then FunctionsInstance.SendKey(MTargetName, MParameter)
        else if Name = FN_PAGEUP then Result := FunctionsInstance.ScrollPage(Name, MTargetName)
        else if Name = FN_PAGEDN then Result := FunctionsInstance.ScrollPage(Name, MTargetName)
        else if Name = FN_PAGELF then Result := FunctionsInstance.ScrollPage(Name, MTargetName)
        else if Name = FN_PAGERT then Result := FunctionsInstance.ScrollPage(Name, MTargetName)
        else
          raise EFunctionException.Create('Performing unsupported function: ' + Name +
                                 ' to control ' + TargetName + ' (' + Description + ')');
      end
      else if FTargetType = ttContainer then
      begin
        if Name = FN_SHOWCNTNR then FunctionsInstance.ShowContainer(MTargetName)
        else if Name = FN_HIDECNTNR then FunctionsInstance.HideContainer(MTargetName)
        else
          raise EFunctionException.Create('Performing unsupported function: ' + Name +
                                 ' to container ' + TargetName + ' (' + Description + ')');
      end
      else if FTargetType = ttScreen then
      begin
        if Name = FN_JUMP then FunctionsInstance.Jump(MTargetName)
        else if Name = FN_IFJUMP then FunctionsInstance.ConditionalJump(MTargetName, MParameter)
        else if Name = FN_OVERLAY then FunctionsInstance.Overlay(MTargetName)
        else if Name = FN_CLOSE then FunctionsInstance.Close(MTargetName)
        else
          raise EFunctionException.Create('Performing unsupported function: ' + Name +
                                 ' to screen ' + TargetName + ' (' + Description + ')');
      end
      else if FTargetType = ttDescriptor then
      begin
        if Name = FN_LINK then FunctionsInstance.Link(MTargetName)
        else
          raise EFunctionException.Create('Performing unsupported function: ' + Name +
                                 ' to descriptor ' + TargetName + ' (' + Description + ')');
      end
      else if FTargetType = ttSound then
      begin
        if Name = FN_SOUND then FunctionsInstance.Sound(MTargetName)
        else
          raise EFunctionException.Create('Performing unsupported function: ' + Name +
                                 ' to sound ' + TargetName + ' (' + Description + ')');
      end
      else if FTargetType = ttValue then
      begin
        if Name = FN_DELAY then Result := FunctionsInstance.Delay(MTargetName)
        else
          raise EFunctionException.Create('Performing unsupported function: ' + Name +
                                 ' to value ' + TargetName + ' (' + Description + ')');
      end
      else if FTargetType = ttMeta then
      begin
        if Name = FN_LOADMETA then FunctionsInstance.LoadMeta(MTargetName)
        else if Name = FN_NEXTMETA then FunctionsInstance.NextMeta(MTargetName, MParameter)
        else if Name = FN_PREVMETA then FunctionsInstance.PrevMeta(MTargetName, MParameter)
        else if Name = FN_INCMETA then FunctionsInstance.IncMeta(MTargetName, MParameter)
        else if Name = FN_DECMETA then FunctionsInstance.DecMeta(MTargetName, MParameter)
        else if Name = FN_SAVEMETA then FunctionsInstance.SaveMeta(MTargetName)
        else if Name = FN_SETMETA then FunctionsInstance.SetMeta(MTargetName, MParameter)
        else if Name = FN_CLEARMETA then FunctionsInstance.ClearMeta(MTargetName)
        else
          raise EFunctionException.Create('Performing unsupported function: ' + Name +
                                 ' to meta ' + TargetName + ' (' + Description + ')');
      end
      else if FTargetType = ttPlaylist then
      begin
        if Name = FN_CLEARPL then FunctionsInstance.ClearPlaylist(MTargetName)
        else if Name = FN_ADDTOPL then FunctionsInstance.AddToPlaylist(MTargetName)
        else if Name = FN_APPENDPL then FunctionsInstance.AppendPlaylist(MTargetName)
        else if Name = FN_INSERTPL then FunctionsInstance.InsertPlaylist(MTargetName)
        else if Name = FN_REPLACEPL then FunctionsInstance.ReplacePlaylist(MTargetName)
        else if Name = FN_RESTARTPL then FunctionsInstance.RestartPlaylist(MTargetName)
        else if Name = FN_PLAY then FunctionsInstance.PlayMedia(MTargetName)
        else if Name = FN_PAUSE then FunctionsInstance.PauseMedia(MTargetName)
        else if Name = FN_PLAYPAUSE then FunctionsInstance.PlayPauseMedia(MTargetName)
        else if Name = FN_FFWD then FunctionsInstance.FForwardMedia(MTargetName)
        else if Name = FN_RWND then FunctionsInstance.RewindMedia(MTargetName)
        else if Name = FN_FSKIP then FunctionsInstance.ForwardSkipMedia(MTargetName)
        else if Name = FN_BSKIP then FunctionsInstance.BackwardSkipMedia(MTargetName)
        else if Name = FN_STOP then FunctionsInstance.StopMedia(MTargetName)
        else
          raise EFunctionException.Create('Performing unsupported function: ' + Name +
                                 ' to playlist ' + TargetName + ' (' + Description + ')');
      end
      else if FTargetType = ttMacro then
      begin
        if Name = FN_MACRO then FunctionsInstance.CallMacro(MTargetName)
        else if Name = FN_IFMACRO then FunctionsInstance.ConditionalMacro(MTargetName, MParameter)
        else
          raise EFunctionException.Create('Performing unsupported function: ' + Name +
                                 ' to macro ' + TargetName + ' (' + Description + ')');
      end
      else if FTargetType = ttPlugin then
      begin
        if Name = FN_LOADPLUG then Result := FunctionsInstance.LoadPlugin(MTargetName)
        else if Name = FN_UNLDPLUG then Result := FunctionsInstance.UnloadPlugin(MTargetName)
        else if Name = FN_STARTPLUG then Result := FunctionsInstance.StartPlugin(MTargetName)
        else if Name = FN_STOPPLUG then Result := FunctionsInstance.StopPlugin(MTargetName)
        else if Name = FN_UPDTPLUG then Result := FunctionsInstance.UpdatePlugin(MTargetName)
        else if Name = FN_SHOWPLUG then Result := FunctionsInstance.ShowPlugin(MTargetName, MParameter)
        else if Name = FN_HIDEPLUG then Result := FunctionsInstance.HidePlugin(MTargetName, MParameter)
        else
          raise EFunctionException.Create('Performing unsupported function: ' + Name +
                                 'to plugin ' + TargetName + ' (' + Description + ')');
      end
      else if FTargetType = ttAction then
      begin
        if Name = FN_ACTION then FunctionsInstance.CallAction(MTargetName)
        else
          raise EFunctionException.Create('Performing unsupported function: ' + Name +
                                 'to action ' + TargetName + ' (' + Description + ')');
      end
      else if FTargetType = ttTimer then
      begin
        if Name = FN_STARTTMR then FunctionsInstance.StartTimer(MTargetName)
        else if Name = FN_RESETTMR then FunctionsInstance.ResetTimer(MTargetName)
        else if Name = FN_STOPTMR then FunctionsInstance.StopTimer(MTargetName)
        else
          raise EFunctionException.Create('Performing unsupported function: ' + Name +
                                 'to timer ' + TargetName + ' (' + Description + ')');
      end
      else if FTargetType = ttText then
      begin
        if Name = FN_INTERNET then FunctionsInstance.BrowseInternet(MTargetName)
        else
          raise EFunctionException.Create('Performing unsupported function: ' + Name +
                                 'to text ' + TargetName + ' (' + Description + ')');
      end
      else
        raise EFunctionException.Create('Performing unsupported function: ' + Name +
                               ' to unkown target type ' + TargetName + ' (' + Description + ')');
    end
    else
      raise EFunctionException.Create('Empty target name passed to function ' + Name)
  else
    raise Exception.Create('No target name expected for function ' + Name);
end;

procedure TAHMFunction.Assign(Source: TPersistent);
begin
  inherited Assign(Source);

  with Source as TAHMFunction do
  begin
    Self.TargetType := TargetType;
  end;
end;

procedure TAHMFunction.SaveAsXML(Lines: TStrings);
var
  oAncs: TAHMFunction;
  sWork: String;
begin
  // Determine which ancestor to compare against - real or empty
  oAncs := TAHMFunction(ObjectToCompare);

  // We need to explicitly save comments in overridden method
  SaveXMLComments(Lines);

  // Not using BeginTag, so need to add name manually
  sWork := TGB+TAG_FUNCTION+SPC+ATTR_NAME+ATB + EncodeXML(Name);

  // Do we need to specify target type?
  if (FTargetType <> ttNone) and (FTargetType <> oAncs.TargetType) then
    sWork := sWork + ATN+ATTR_TARGET+ATB + TargetType2Str(FTargetType);

  // Need to redo description and inheritance here, since not using BeginTag
  if (Description <> '') and (Description <> oAncs.Description) then
    sWork := sWork + ATN+ATTR_DESC+ATB + EncodeXML(Description);
  if (Ancestor <> nil) then
    sWork := sWork + ATN+ATTR_INHERIT+ATB + EncodeXML(Ancestor.Name);

  Lines.Add(TABS[2] + sWork + ATE);
end;

procedure TAHMFunction.ParseFromXML(XML: TAHMXMLHelper);
var
  sTarget: String;
begin
  // Parse & convert function target type
  sTarget := XML.GetAttribute(ATTR_TARGET);
  if sTarget <> '' then TargetType := Str2TargetType(sTarget);
end;

//----------------------------------------------------------------------------//

procedure TAHMFunctions.Quit;
begin
  // Ask the application manager to terminate the application
  if not ApplicationManagerInstance.DesignerMode then
    ApplicationManagerInstance.Terminate;
end;

procedure TAHMFunctions.Restart;
begin
  // Ask the application manager to restart the application
  if not ApplicationManagerInstance.DesignerMode then
    ApplicationManagerInstance.Restart;
end;

procedure TAHMFunctions.Sleep;
begin
  // Ask the application manager to terminate & send windows to sleep
  if not ApplicationManagerInstance.DesignerMode then
    ApplicationManagerInstance.Terminate(wsSleep);
end;

procedure TAHMFunctions.PowerOff;
begin
  // Ask the application manager to terminate & power off windows
  if not ApplicationManagerInstance.DesignerMode then
    ApplicationManagerInstance.Terminate(wsPowerOff);
end;

procedure TAHMFunctions.Reboot;
begin
  // Ask the application manager to terminate & reboot windows
  if not ApplicationManagerInstance.DesignerMode then
    ApplicationManagerInstance.Terminate(wsReboot);
end;

procedure TAHMFunctions.Hibernate;
begin
  // Ask the application manager to terminate & hibernate windows
  if not ApplicationManagerInstance.DesignerMode then
    ApplicationManagerInstance.Terminate(wsHibernate);
end;

procedure TAHMFunctions.WakeScreen;
begin
  ApplicationManagerInstance.UIManager.ScreenSaver.DidSomething;
end;
  
procedure TAHMFunctions.SleepScreen;
begin
  ApplicationManagerInstance.UIManager.ScreenSaver.GoIdle;
end;
  
procedure TAHMFunctions.SwitchApplication;
begin
  // Simulate windows alt-tab between running applications
  FocusAppWindow(FApplicationIndex);
end;

procedure TAHMFunctions.CallAction(ActionName: String);
begin
  ApplicationManagerInstance.ActionManager.ProcessAction(ActionName);
end;

procedure TAHMFunctions.PlayMedia(PlaylistName: String);
begin
  ApplicationManagerInstance.MediaManager.Play(Str2StreamType(PlaylistName));
end;

procedure TAHMFunctions.PauseMedia(PlaylistName: String);
begin
  ApplicationManagerInstance.MediaManager.Pause(Str2StreamType(PlaylistName));
end;

procedure TAHMFunctions.PlayPauseMedia(PlaylistName: String);
begin
  ApplicationManagerInstance.MediaManager.PlayPause(Str2StreamType(PlaylistName));
end;

procedure TAHMFunctions.FForwardMedia(PlaylistName: String);
begin
  ApplicationManagerInstance.MediaManager.FastForward(Str2StreamType(PlaylistName));
end;

procedure TAHMFunctions.RewindMedia(PlaylistName: String);
begin
  ApplicationManagerInstance.MediaManager.Rewind(Str2StreamType(PlaylistName));
end;

procedure TAHMFunctions.ForwardSkipMedia(PlaylistName: String);
begin
  ApplicationManagerInstance.MediaManager.SkipForward(Str2StreamType(PlaylistName));
end;

procedure TAHMFunctions.BackwardSkipMedia(PlaylistName: String);
begin
  ApplicationManagerInstance.MediaManager.SkipBackward(Str2StreamType(PlaylistName));
end;

procedure TAHMFunctions.StopMedia(PlaylistName: String);
begin
  ApplicationManagerInstance.MediaManager.Stop(Str2StreamType(PlaylistName));
end;

procedure TAHMFunctions.MuteMedia;
begin
  ApplicationManagerInstance.MediaManager.ToggleMute;
end;

procedure TAHMFunctions.IncreaseVolume;
begin
  ApplicationManagerInstance.MediaManager.IncreaseVolume;
end;

procedure TAHMFunctions.DecreaseVolume;
begin
  ApplicationManagerInstance.MediaManager.DecreaseVolume;
end;

procedure TAHMFunctions.ClearPlaylist(PlaylistName: String);
begin
  ApplicationManagerInstance.MediaManager.ClearPlaylist(Str2StreamType(PlaylistName));
end;

procedure TAHMFunctions.AddToPlaylist(PlaylistName: String);
begin
  ApplicationManagerInstance.MediaManager.AddToPlaylist(Str2StreamType(PlaylistName));
end;

procedure TAHMFunctions.AppendPlaylist(PlaylistName: String);
begin
  ApplicationManagerInstance.MediaManager.AppendPlaylist(Str2StreamType(PlaylistName));
end;

procedure TAHMFunctions.InsertPlaylist(PlaylistName: String);
begin
  ApplicationManagerInstance.MediaManager.InsertPlaylist(Str2StreamType(PlaylistName));
end;

procedure TAHMFunctions.ReplacePlaylist(PlaylistName: String);
begin
  ApplicationManagerInstance.MediaManager.ReplacePlaylist(Str2StreamType(PlaylistName));
end;

procedure TAHMFunctions.RestartPlaylist(PlaylistName: String);
begin
  ApplicationManagerInstance.MediaManager.RestartPlaylist(Str2StreamType(PlaylistName));
end;

procedure TAHMFunctions.ShowDiagnostics;
var
  lDiag: Boolean;
begin
  // Toggle display of diagnostics in UIManager
  lDiag := ApplicationManagerInstance.UIManager.DisplayDiagnostics;
  ApplicationManagerInstance.UIManager.DisplayDiagnostics := not lDiag;
end;

procedure TAHMFunctions.ShowConfiguration;
begin
  // Display advanced settings configuration dialog
  ApplicationManagerInstance.ConfigureSettings;
end;

procedure TAHMFunctions.ResetScreenPosition;
begin
  // Reset default screen scale and position
  ApplicationManagerInstance.PreferencesManager.ResetDefaultScreenPosition;
  ApplicationManagerInstance.ConfigureScreenPosition;

  // Repopulate settings metadata in case we are currently editing
  ApplicationManagerInstance.SettingsManager.LoadMetaData(META_POSITION);
end;

procedure TAHMFunctions.MoveFocus(Direction: String);
var
  i: Integer;
  x, y, zh, zv, ozh, ozv: Single;
  dir: TAHMDirection;
  lStore: Boolean;
  oScreen: TAHMScreenLayout;
  oControls: TAHMControlList;
  oCurr, oMatch, oThis: TAHMInteractiveControl;
  sOverride: String;
begin
//TODO - we should move the bulk of this code to Screen class
  // Convert passed function name to a direction
  if Direction = FN_FOCUSUP then dir := diUp
  else if Direction = FN_FOCUSDN then dir := diDown
  else if Direction = FN_FOCUSLF then dir := diLeft
  else if Direction = FN_FOCUSRT then dir := diRight
  else Exit;

  // Get current screen - abort if we don't have one
  oScreen := ApplicationManagerInstance.UIManager.CurrentScreen;
  if oScreen = nil then Exit;
  oCurr := oScreen.FocusControl;

  // See if selected control wants to scroll instead of moving focus
  if Assigned(oCurr) and oCurr.Scroll(dir) then Exit;

  // Now we need to obtain current screen controls and focussed control
  oScreen.RefreshNavigableControls;
  oControls := oScreen.NavigableControls;

  // Choose an arbitrary control and quit if none currently selected
  if oCurr = nil then
  begin
    if oControls.Count > 0 then
      oScreen.FocusControl := TAHMInteractiveControl(oControls.Objects[0]);
    Exit;
  end;

  // If control overrides navigation control in selected direction then use it
  // See whether current control specifies an override in selection direction
  case dir of
    diUp: sOverride := oCurr.NavigateUpName;
    diDown: sOverride := oCurr.NavigateDownName;
    diLeft: sOverride := oCurr.NavigateLeftName;
    diRight: sOverride := oCurr.NavigateRightName;
    else sOverride := '';
  end;

  // If we want to override navigation then lookup and focus appropriate control
  if sOverride <> '' then
  begin
    oCurr := TAHMInteractiveControl(oControls.GetControl(sOverride));
    if Assigned(oCurr) then oScreen.FocusControl := oCurr;
    Exit;
  end;

  // Avoid compiler warning
  ozh := -1; ozv := -1;

  // Iterate through all controls looking for best match
  oMatch := nil;
  for i := 0 to Pred(oControls.Count) do
  begin
    // Get next control in list and compute weighted distance from current
    oThis := TAHMInteractiveControl(oControls.Objects[i]);
    x := Abs(oThis.CentreX - oCurr.CentreX);
    y := Abs(oThis.CentreY - oCurr.CentreY);
    // Approximate distance using 2:1 weighting in direction of travel
    zv := (2*x + y);
    zh := (x + 2*y);
    lStore := False;

    case dir of
      diUp: lStore := (oThis.CentreY < oCurr.CentreY) and ((oMatch = nil) or (zv < ozv));
      diDown: lStore := (oThis.CentreY > oCurr.CentreY) and ((oMatch = nil) or (zv < ozv));
      diLeft: lStore := (oThis.CentreX < oCurr.CentreX) and ((oMatch = nil) or (zh < ozh));
      diRight: lStore := (oThis.CentreX > oCurr.CentreX) and ((oMatch = nil) or (zh < ozh));
    end;

    // Should we store this new coordinate?
    if lStore then
    begin
      oMatch := oThis;
      ozh := zh;
      ozv := zv;
    end;
  end;

  // We should now have the next nearest matching object, if there is one
  if oMatch <> nil then
    oScreen.FocusControl := oMatch;
end;

function TAHMFunctions.ScrollPage(Direction: String; ControlName: String): TAHMFunctionResult;
var
  dir: TAHMDirection;
  oControl: TAHMControl;
begin
  // Set default result - failure
  Result := frFailed;

  // Convert passed function name to a direction
  if Direction = FN_PAGEUP then dir := diUp
  else if Direction = FN_PAGEDN then dir := diDown
  else if Direction = FN_PAGELF then dir := diLeft
  else if Direction = FN_PAGERT then dir := diRight
  else Exit;

  // Lookup requested control in current screen controls
  oControl := ApplicationManagerInstance.UIManager.GetControl(ControlName, False);

  if oControl <> nil then
  begin
    if oControl.PageScroll(dir) then Result := frSuccess;
  end
  else
    LogWarn('Can''t page scroll unknown control: ' + ControlName);
end;

procedure TAHMFunctions.SetContainerVisible(ContainerName: String; Visible: Boolean);
var
  oContainer: TAHMContainer;
begin
  // Lookup requested container in list of all known containers
  oContainer := ApplicationManagerInstance.UIManager.GetContainer(ContainerName, False);

  if oContainer <> nil then
    oContainer.Visible := Visible
  else
    LogWarn('Can''t change visibility of unknown container: ' + ContainerName);
end;

procedure TAHMFunctions.SetControlVisible(ControlName: String; Visible: Boolean);
var
  oControl: TAHMControl;
begin
  // Lookup requested control in list of all known controls
  oControl := ApplicationManagerInstance.UIManager.GetControl(ControlName, False);

  if oControl <> nil then
    oControl.Visible := Visible
  else
    LogWarn('Can''t change visibility of unknown control: ' + ControlName);
end;

procedure TAHMFunctions.SetControlEnabled(ControlName: String; Enabled: Boolean);
var
  oControl: TAHMControl;
begin
  // Lookup requested control in list of all known controls
  oControl := ApplicationManagerInstance.UIManager.GetControl(ControlName, False);

  if oControl is TAHMInteractiveControl then
    TAHMInteractiveControl(oControl).Enabled := Enabled
  else
    LogWarn('Can''t change enabled state of non-interactive control: ' + ControlName);
end;

procedure TAHMFunctions.SetControlMouseEnabled(ControlName: String; Enabled: Boolean);
var
  oControl: TAHMControl;
begin
  // Lookup requested control in list of all known controls
  oControl := ApplicationManagerInstance.UIManager.GetControl(ControlName, False);

  if oControl is TAHMInteractiveControl then
    TAHMInteractiveControl(oControl).MouseEnabled := Enabled
  else
    LogWarn('Can''t change mouse enabled state of non-interactive control: ' + ControlName);
end;

procedure TAHMFunctions.SetControlChecked(ControlName: String; Checked: TAHMCheckState);
var
  oControl: TAHMControl;
begin
  // Lookup requested control in list of all known controls
  oControl := ApplicationManagerInstance.UIManager.GetControl(ControlName, False);

  if oControl is TAHMInteractiveControl then
  begin
    case Checked of
      csChecked: TAHMInteractiveControl(oControl).Checked := True;
      csUnChecked: TAHMInteractiveControl(oControl).Checked := False;
      else TAHMInteractiveControl(oControl).Checked := not TAHMInteractiveControl(oControl).Checked;
    end;
  end
  else
    LogWarn('Can''t change checked state of non-interactive control: ' + ControlName);
end;

procedure TAHMFunctions.Focus(ControlName: String);
var
  oControl: TAHMControl;
  oScreen: TAHMScreenLayout;
begin
  // Get current screen - abort if we don't have one
  oScreen := ApplicationManagerInstance.UIManager.CurrentScreen;
  if oScreen = nil then Exit;

  // Lookup requested control in current screen controls
  oControl := ApplicationManagerInstance.UIManager.GetControl(ControlName, True);

  if oControl is TAHMInteractiveControl then
    oScreen.FocusControl := TAHMInteractiveControl(oControl)
  else
    LogWarn('Can''t change focus to non-interactive control: ' + ControlName);
end;

procedure TAHMFunctions.Press(ControlName: String);
var
  oControl: TAHMControl;
begin
  // Lookup requested control in list of all known controls
  oControl := ApplicationManagerInstance.UIManager.GetControl(ControlName, False);

  if oControl is TAHMInteractiveControl then
    TAHMInteractiveControl(oControl).Activate
  else
    LogWarn('Can''t press (activate) non-interactive control: ' + ControlName);
end;

procedure TAHMFunctions.Check(ControlName: String);
begin
  SetControlChecked(ControlName, csChecked);
end;

procedure TAHMFunctions.UnCheck(ControlName: String);
begin
  SetControlChecked(ControlName, csUnChecked);
end;

procedure TAHMFunctions.ToggleCheck(ControlName: String);
begin
  SetControlChecked(ControlName, csToggle);
end;

procedure TAHMFunctions.Cancel(ControlName: String);
var
  oControl: TAHMControl;
begin
  // Lookup requested control in current screen controls
  oControl := ApplicationManagerInstance.UIManager.GetControl(ControlName, True);

  if oControl is TAHMInteractiveControl then
    TAHMInteractiveControl(oControl).Cancel
  else
    LogWarn('Can''t invoke cancel on non-interactive control: ' + ControlName);
end;

procedure TAHMFunctions.Parent(ControlName: String);
var
  oControl: TAHMControl;
begin
  // Lookup requested control in current screen controls
  oControl := ApplicationManagerInstance.UIManager.GetControl(ControlName, True);

  if oControl is TAHMMediaGridControl then
    TAHMMediaGridControl(oControl).Parent
  else
    LogWarn('Can''t invoke parent, this is not a media grid control: ' + ControlName);
end;

procedure TAHMFunctions.Root(ControlName: String);
var
  oControl: TAHMControl;
begin
  // Lookup requested control in current screen controls
  oControl := ApplicationManagerInstance.UIManager.GetControl(ControlName, True);

  if oControl is TAHMMediaGridControl then
    TAHMMediaGridControl(oControl).Root
  else
    LogWarn('Can''t invoke root, this is not a media grid control: ' + ControlName);
end;

procedure TAHMFunctions.Enable(ControlName: String);
begin
  SetControlEnabled(ControlName, True);
end;

procedure TAHMFunctions.Disable(ControlName: String);
begin
  SetControlEnabled(ControlName, False);
end;

procedure TAHMFunctions.MouseEnable(ControlName: String);
begin
  SetControlMouseEnabled(ControlName, True);
end;

procedure TAHMFunctions.MouseDisable(ControlName: String);
begin
  SetControlMouseEnabled(ControlName, False);
end;

procedure TAHMFunctions.Show(ControlName: String);
begin
  SetControlVisible(ControlName, True);
end;

procedure TAHMFunctions.Hide(ControlName: String);
begin
  SetControlVisible(ControlName, False);
end;

procedure TAHMFunctions.ShowContainer(ContainerName: String);
begin
  SetContainerVisible(ContainerName, True);
end;

procedure TAHMFunctions.HideContainer(ContainerName: String);
begin
  SetContainerVisible(ContainerName, False);
end;

procedure TAHMFunctions.Refresh(ControlName: String);
var
  oControl: TAHMControl;
begin
  // Lookup requested control in current screen controls
  oControl := ApplicationManagerInstance.UIManager.GetControl(ControlName, False);

  if oControl <> nil then
    oControl.Invalidate
  else
    LogWarn('Can''t refresh contents of unknown control: ' + ControlName);
end;

procedure TAHMFunctions.SetStyle(ControlName: String; StyleName: String);
var
  oControl: TAHMControl;
  oOldStyle: TAHMUIDisplayObject;
begin
  // Lookup requested control in current screen controls
  oControl := ApplicationManagerInstance.UIManager.GetControl(ControlName, False);

  if oControl <> nil then
  begin
    // Preserve current style in case we don't find new style
    oOldStyle := oControl.Style;
    oControl.StyleName := StyleName;
    if oControl.Style = nil then
    begin
      oControl.Style := oOldStyle; // Restore old style
      LogWarn('Can''t change control ' + ControlName + ' to unknown style: ' + StyleName);
    end;
  end
  else
    LogWarn('Can''t change style of unknown control: ' + ControlName);
end;

procedure TAHMFunctions.AnimateControl(ControlName: String; AnimatorName: String);
var
  oAnimator: TAHMAnimator;
begin
  // Lookup requested animator object
  oAnimator := ApplicationManagerInstance.ThemeManager.InterfaceDescriptor.AnimatorList.GetAnimator(AnimatorName);

  if oAnimator <> nil then
    ApplicationManagerInstance.AnimationEngine.AnimateControl(ControlName, oAnimator)
  else
    LogWarn('Can''t animate control using unknown animator: ' + AnimatorName);
end;

procedure TAHMFunctions.AnimateContainer(ContainerName: String; AnimatorName: String);
var
  oAnimator: TAHMAnimator;
begin
  // Lookup requested animator object
  oAnimator := ApplicationManagerInstance.ThemeManager.InterfaceDescriptor.AnimatorList.GetAnimator(AnimatorName);

  if oAnimator <> nil then
    ApplicationManagerInstance.AnimationEngine.AnimateContainer(ContainerName, oAnimator)
  else
    LogWarn('Can''t animate container using unknown animator: ' + AnimatorName);
end;

procedure TAHMFunctions.SendKey(ControlName: String; KeyName: String);
var
  oControl: TAHMControl;
begin
  // Lookup requested control in current screen controls
  oControl := ApplicationManagerInstance.UIManager.GetControl(ControlName, False);

  if oControl is TAHMInteractiveControl then
    TAHMInteractiveControl(oControl).SendKey(KeyName)
  else
    LogWarn('Can''t send keycodes to non-interactive control: ' + ControlName);
end;

procedure TAHMFunctions.Link(DescriptorName: String);
begin
  ApplicationManagerInstance.UIManager.LoadDescriptor(DescriptorName);
end;

procedure TAHMFunctions.Jump(ScreenName: String);
begin
  // Jump to requested screen (if it exists)
  ApplicationManagerInstance.UIManager.JumpToScreen(ScreenName);
end;

procedure TAHMFunctions.ConditionalJump(ScreenName: String; Expression: String);
begin
  // Test condition before performing jump function
  if ApplicationManagerInstance.ExpressionManager.EvaluateCondition(Expression) then
    Jump(ScreenName);
end;

procedure TAHMFunctions.Overlay(ScreenName: String);
var
  oScreen: TAHMScreenLayout;
begin
  // Lookup requested screen layout
  oScreen := ApplicationManagerInstance.ThemeManager.InterfaceDescriptor.ScreenList.GetScreenLayout(ScreenName);

  // Check this screen exists
  if Assigned(oScreen) then
    ApplicationManagerInstance.UIManager.OverlayScreen(oScreen.Name)
  else
    LogWarn('Can''t overlay unknown screen name: ' + ScreenName);
end;

procedure TAHMFunctions.Close(ScreenName: String);
var
  oScreen: TAHMScreenLayout;
begin
  // Lookup requested screen layout
  oScreen := ApplicationManagerInstance.ThemeManager.InterfaceDescriptor.ScreenList.GetScreenLayout(ScreenName);

  // Check this screen exists
  if Assigned(oScreen) then
    ApplicationManagerInstance.UIManager.CloseScreen(oScreen.Name)
  else
    LogWarn('Can''t close unknown screen name: ' + ScreenName);
end;

procedure TAHMFunctions.Sound(SoundName: String);
var
  oSound: TAHMSound;
begin
  // Lookup requested sound object
  oSound := ApplicationManagerInstance.ThemeManager.AudioDescriptor.SoundList.GetSound(SoundName);

  // Check this sound exists
  if Assigned(oSound) then
    oSound.PlaySound
  else
    LogWarn('Can''t play unknown sound name: ' + SoundName);
end;

function TAHMFunctions.Delay(Millisecs: String): TAHMFunctionResult;
begin
  // Set default result - time period not elapsed yet
  Result := frPartial;

  // Are we already running a delay loop?
  if FDelayRunning then
  begin
    // Already running a delay - have we passed required time?
    if TimerInstance.HasDurationElapsed(FDelayTimestamp, StrToIntDef(Millisecs, 0)) then
    begin
      // Set appropriate result & switch off timer flag
      Result := frSuccess;
      FDelayRunning := False;
    end;
  end
  else
  begin
    // No delay running yet, so start a new one
    FDelayTimestamp := TimerInstance.CurrentTimestamp;
    FDelayRunning := True;
  end;
end;

procedure TAHMFunctions.SetMeta(MetaItem: String; Value: String);
begin
  // Set meta item to passed value
  ApplicationManagerInstance.MetaDataManager.SetMetaValue(MetaItem, Value);
end;

procedure TAHMFunctions.ClearMeta(MetaItem: String);
begin
  // Clear passed meta item
  ApplicationManagerInstance.MetaDataManager.ClearMetaData(MetaItem);
end;

procedure TAHMFunctions.NextMeta(MetaItem: String; MetaGroup: String);
begin
  // Advance to next meta item in group
  ApplicationManagerInstance.MetaDataManager.NextMeta(MetaItem, MetaGroup);
end;

procedure TAHMFunctions.PrevMeta(MetaItem: String; MetaGroup: String);
begin
  // Advance to previous meta item in group
  ApplicationManagerInstance.MetaDataManager.PrevMeta(MetaItem, MetaGroup);
end;

procedure TAHMFunctions.IncMeta(MetaItem: String; MetaGroup: String);
begin
  // Increment meta value within range specified by group
  ApplicationManagerInstance.MetaDataManager.IncMeta(MetaItem, MetaGroup);
end;

procedure TAHMFunctions.DecMeta(MetaItem: String; MetaGroup: String);
begin
  // Decrement meta value within range specified by group
  ApplicationManagerInstance.MetaDataManager.DecMeta(MetaItem, MetaGroup);
end;

procedure TAHMFunctions.LoadMeta(MetaGroup: String);
begin
  // Ask settings manager to repopulate metadata group
  ApplicationManagerInstance.SettingsManager.LoadMetaData(MetaGroup);
end;

procedure TAHMFunctions.SaveMeta(MetaGroup: String);
begin
  // Ask settings manager to store metadata group
  ApplicationManagerInstance.SettingsManager.SaveMetaData(MetaGroup);
end;

procedure TAHMFunctions.CallMacro(MacroName: String);
begin
  // Ask macro list to process macro functions
  ApplicationManagerInstance.ThemeManager.InterfaceDescriptor.MacroList.InvokeMacro(MacroName);
end;

procedure TAHMFunctions.ConditionalMacro(MacroName: String; Expression: String);
begin
  // Ask macro list to process conditional macro functions
  ApplicationManagerInstance.ThemeManager.InterfaceDescriptor.MacroList.InvokeConditionalMacro(MacroName, Expression);
end;

function TAHMFunctions.LoadPlugin(PluginName: String): TAHMFunctionResult;
begin
  // Ask plugin manager to load specified plugin
  if ApplicationManagerInstance.PluginManager.LoadPlugin(PluginName) then
    Result := frSuccess
  else
    Result := frFailed;
end;

function TAHMFunctions.UnloadPlugin(PluginName: String): TAHMFunctionResult;
begin
  // Ask plugin manager to unload specified plugin
  if ApplicationManagerInstance.PluginManager.UnloadPlugin(PluginName) then
    Result := frSuccess
  else
    Result := frFailed;
end;

function TAHMFunctions.StartPlugin(PluginName: String): TAHMFunctionResult;
begin
  // Ask plugin manager to start specified plugin
  if ApplicationManagerInstance.PluginManager.StartPlugin(PluginName) then
    Result := frSuccess
  else
    Result := frFailed;
end;

function TAHMFunctions.StopPlugin(PluginName: String): TAHMFunctionResult;
begin
  // Ask plugin manager to stop specified plugin
  if ApplicationManagerInstance.PluginManager.StopPlugin(PluginName) then
    Result := frSuccess
  else
    Result := frFailed;
end;

function TAHMFunctions.UpdatePlugin(PluginName: String): TAHMFunctionResult;
begin
  // Ask plugin manager to update specified plugin
  if ApplicationManagerInstance.PluginManager.UpdatePlugin(PluginName) then
    Result := frSuccess
  else
    Result := frFailed;
end;

function TAHMFunctions.ShowPlugin(PluginName, ControlName: String): TAHMFunctionResult;
begin
  // Ask plugin manager to show specified plugin
  if ApplicationManagerInstance.PluginManager.ShowPlugin(PluginName, ControlName) then
    Result := frSuccess
  else
    Result := frFailed;
end;

function TAHMFunctions.HidePlugin(PluginName, ControlName: String): TAHMFunctionResult;
begin
  // Ask plugin manager to hide specified plugin
  if ApplicationManagerInstance.PluginManager.HidePlugin(PluginName, ControlName) then
    Result := frSuccess
  else
    Result := frFailed;
end;

procedure TAHMFunctions.BrowseInternet(URL: String);
begin
  // Very quick and dirty for now.. just launch external browser
  DisplayBrowser(URL);
end;

procedure TAHMFunctions.StartTimer(TimerName: String);
var
  oTimer: TAHMTimer;
begin
  // Lookup requested timer object
  oTimer := ApplicationManagerInstance.ThemeManager.InterfaceDescriptor.TimerList.GetTimer(TimerName);

  if oTimer <> nil then
    ApplicationManagerInstance.TimerManager.StartTimer(oTimer)
  else
    LogWarn('Can''t start unknown timer: ' + TimerName);
end;

procedure TAHMFunctions.ResetTimer(TimerName: String);
var
  oTimer: TAHMTimer;
begin
  // Lookup requested timer object
  oTimer := ApplicationManagerInstance.ThemeManager.InterfaceDescriptor.TimerList.GetTimer(TimerName);

  if oTimer <> nil then
    ApplicationManagerInstance.TimerManager.ResetTimer(oTimer)
  else
    LogWarn('Can''t reset unknown timer: ' + TimerName);
end;

procedure TAHMFunctions.StopTimer(TimerName: String);
var
  oTimer: TAHMTimer;
begin
  // Lookup requested timer object
  oTimer := ApplicationManagerInstance.ThemeManager.InterfaceDescriptor.TimerList.GetTimer(TimerName);

  if oTimer <> nil then
    ApplicationManagerInstance.TimerManager.StopTimer(oTimer)
  else
    LogWarn('Can''t stop unknown timer: ' + TimerName);
end;

procedure TAHMFunctions.AddRadioPreset;
begin
  // Delegate to media manager
  ApplicationManagerInstance.MediaManager.Radio.AddPreset;
end;

procedure TAHMFunctions.DeleteRadioPreset;
begin
  // Delegate to media manager
  ApplicationManagerInstance.MediaManager.Radio.DeletePreset;
end;

initialization
  FunctionsInstance := TAHMFunctions.Create;

finalization
  FunctionsInstance.Free;

end.
